package com.ns.basic.log.utils;

import com.alibaba.fastjson2.JSON;
import com.ns.basic.log.entry.ApiLog;
import com.ns.basic.log.service.IApiLogService;
import com.ns.core.annotations.ReqLog;
import com.ns.core.current.config.SymbolsMark;
import com.ns.core.current.config.SysConst;
import com.ns.core.id.IdWorker;
import com.ns.core.project.MapperService;
import com.ns.core.utils.StpUtil;
import com.ns.core.utils.bean.ServiceUtil;
import com.ns.core.utils.cache.RedisUtil;
import com.ns.core.utils.jwt.SystemUtil;
import com.ns.core.utils.sg.IpUtil;
import com.ns.core.utils.sg.NbUtil;
import com.ns.core.utils.sg.ResultError;
import com.ns.core.utils.sg.ResultOk;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * 工具类：api请求记录表
 *
 * @author lw
 */
@Component
public class ApiLogUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApiLogUtil.class);

    /**
     * 请求开始时调用，开始计时
     */
    public static void startRequest(JoinPoint joinPoint, ReqLog reqLog) {
        Object[] args = joinPoint.getArgs();
        // 接收到请求
        HttpServletRequest request = IpUtil.getRequest();
        if (reqLog.isRecordFlag()) {
            ApiLog a = new ApiLog();
            a.setSid(StpUtil.getSid());
            a.setName(reqLog.module() + SymbolsMark.DASH + reqLog.name());
            a.setIp(IpUtil.getIP(request));
            a.setApi(request.getRequestURI());
            String param = "";
            if (args != null && args.length > 0) {
                if (!(args[0] instanceof HttpServletRequest)) {
                    if (joinPoint.getArgs()[0] instanceof MultipartFile) {
                        param = joinPoint.getArgs()[0].toString();
                    } else {
                        if (Objects.nonNull(joinPoint.getArgs()[0])) {
                            param = JSON.toJSONString(joinPoint.getArgs()[0]);
                        }
                    }
                }
            }
            a.setParam(param);
            if (reqLog.authFlag()) {
                String token = StpUtil.getTokenValue();
                // 判断是否登录接口
                if (StringUtils.hasText(token)) {
                    String redisToken = RedisUtil.get(SysConst.TOKEN + SymbolsMark.AT + StpUtil.getLoginUserId());
                    // 缓存过期
                    if (!StringUtils.hasText(redisToken)) {
                        throw new ResultError(ResultOk.CODE_NOT_LOGIN, "登录信息已过期,请重新登录!");
                    }
                } else {
                    throw new ResultError(ResultOk.CODE_NOT_LOGIN, "请先登录，在进行操作!");
                }
                a.setToken(token);
                a.setUserId(StpUtil.getLoginUserId());
                a.setRid(StpUtil.getRid());
            }

            a.setHeader(JSON.toJSONString(NbUtil.getHeaderMap(request)));
            a.setType(request.getMethod());
            a.setStartTime(new Date());
            request.setAttribute(SysConst.API_LOG_OBJ_SAVE_KEY, a);
            // 控制台日志
            if (SystemUtil.config.isLogToFile()) {
                LogUtil.info("IP: " + a.getIp() + "\tr-> " + a.getApi() + "\tp-> " + a.getParam());
            }
        }
    }

    /**
     * 请求结束时调用，结束计时
     *
     * @param aj 参数
     */
    public static void endRequest(ResultOk aj) {
        // 读取本次请求的 ApiLog 对象
        HttpServletRequest request = IpUtil.getRequest();
        ApiLog a = (ApiLog) request.getAttribute(SysConst.API_LOG_OBJ_SAVE_KEY);
        if (a == null) {
            return;
        }
        // 保存数据库
        try {
            // 开始结束计时
            a.setResCode(aj.getCode());
            a.setResMsg(aj.getMsg());
            a.setResStr(MapperService.objectMapper.writeValueAsString(aj));
            a.setEndTime(new Date());
            a.setCostTime((int) (a.getEndTime().getTime() - a.getStartTime().getTime()));
            // res 字符串过长时禁止写入
            if (a.getResStr().length() > 50000) {
                a.setResStr("{\"msg\": \"数据过长，无法写入 (length=" + a.getResStr().length() + ")\"}");
            }
            // 输出到控制台
            if (SystemUtil.config.isLogToFile()) {
                LogUtil.info("本次请求耗时：" + ((a.getCostTime() + 0.0) / 1000) + "s, 返回：" + a.getResStr());
            }
            // 输出到数据库
            if (SystemUtil.config.isLogToDb()) {
                IApiLogService apiLogService = ServiceUtil.getBean(IApiLogService.class);
                apiLogService.saveOrUpdate(a);
            }
        } catch (Exception e) {
            LOGGER.error("保存API请求日志失败", e);
        } finally {
            // 清除上下文，防止重复记录日志
            request.removeAttribute(SysConst.API_LOG_OBJ_SAVE_KEY);
        }
    }

    /**
     * 获取当前请求的id
     *
     * @return String
     */
    public static String getCurrReqId() {
        HttpServletRequest request = IpUtil.getRequest();
        String id = (String) request.getAttribute(SysConst.API_LOG_OBJ_SAVE_ID_KEY);
        if (id == null) {
            id = IdWorker.getMillisecond();
            request.setAttribute(SysConst.API_LOG_OBJ_SAVE_ID_KEY, id);
        }
        return id;
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public static boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

    /**
     * 根据雪花算法，返回唯一id
     * (此地方将workerId写死为1，如果你在分布式场景中应用此方法，你需要对workerId生成策略进行改造)
     *
     * @return String
     */
    public static Long getSnowflakeId() {
        return IdWorker.getId();
    }

}
